/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL ES 2.0 Module
 * -------------------------------------------------
 *
 * Copyright 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *//*!
 * \file
 * \brief Texture upload performance tests.
 *
 * \todo [2012-10-01 pyry]
 *  - Test different pixel unpack alignments
 *  - Use multiple textures
 *  - Trash cache prior to uploading from data ptr
 *//*--------------------------------------------------------------------*/

#include "es2pTextureUploadTests.hpp"
#include "tcuTexture.hpp"
#include "tcuTextureUtil.hpp"
#include "tcuTestLog.hpp"
#include "tcuSurface.hpp"
#include "gluTextureUtil.hpp"
#include "gluShaderProgram.hpp"
#include "gluPixelTransfer.hpp"
#include "deStringUtil.hpp"
#include "deRandom.hpp"
#include "deClock.h"
#include "deString.h"

#include "glsCalibration.hpp"

#include "glwEnums.hpp"
#include "glwFunctions.hpp"

#include <algorithm>
#include <vector>

namespace deqp
{
namespace gles2
{
namespace Performance
{

using tcu::Vec2;
using tcu::Vec3;
using tcu::Vec4;
using tcu::IVec4;
using std::string;
using std::vector;
using tcu::TestLog;
using tcu::TextureFormat;
using namespace glw; // GL types

static const int	VIEWPORT_SIZE	= 64;
static const float	quadCoords[] =
{
	-1.0f, -1.0f,
	 1.0f, -1.0f,
	-1.0f,  1.0f,
	 1.0f,  1.0f
};

class TextureUploadCase : public TestCase
{
public:
								TextureUploadCase	(Context& context, const char* name, const char* description, UploadFunction uploadFunction, deUint32 format, deUint32 type, int texSize);
								~TextureUploadCase	(void);

	virtual void				init				(void);
	void						deinit				(void);

	virtual IterateResult		iterate				(void) = 0;
	void						logResults			(void);

protected:
	UploadFunction				m_uploadFunction;
	deUint32					m_format;
	deUint32					m_type;
	int							m_texSize;
	int							m_alignment;

	gls::TheilSenCalibrator		m_calibrator;
	glu::ShaderProgram*			m_program;
	deUint32					m_texture;
	de::Random					m_rnd;
	TestLog&					m_log;

	vector<deUint8>				m_texData;
};

TextureUploadCase::TextureUploadCase (Context& context, const char* name, const char* description, UploadFunction uploadFunction, deUint32 format, deUint32 type, int texSize)
	: TestCase			(context, tcu::NODETYPE_PERFORMANCE, name, description)
	, m_uploadFunction	(uploadFunction)
	, m_format			(format)
	, m_type			(type)
	, m_texSize			(texSize)
	, m_alignment		(4)
	, m_calibrator		()
	, m_program			(DE_NULL)
	, m_texture			(0)
	, m_rnd				(deStringHash(name))
	, m_log				(context.getTestContext().getLog())
{
}

TextureUploadCase::~TextureUploadCase (void)
{
	TextureUploadCase::deinit();
}

void TextureUploadCase::deinit (void)
{
	const glw::Functions& gl = m_context.getRenderContext().getFunctions();

	if (m_program)
	{
		delete m_program;
		m_program = DE_NULL;
	}

	gl.deleteTextures(1, &m_texture);
	m_texture = 0;

	m_texData.clear();
}

void TextureUploadCase::init (void)
{
	const glw::Functions& gl = m_context.getRenderContext().getFunctions();
	int maxTextureSize;
	gl.getIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize);

	if (m_texSize > maxTextureSize)
	{
		m_testCtx.setTestResult(QP_TEST_RESULT_NOT_SUPPORTED, "Unsupported texture size");
		return;
	}

	// Create program

	string vertexShaderSource = "";
	string fragmentShaderSource = "";

	vertexShaderSource.append(	"precision mediump	float;\n"
								"attribute vec2		a_pos;\n"
								"varying   vec2		v_texCoord;\n"
								"\n"
								"void main (void)\n"
								"{\n"
								"	v_texCoord	= a_pos;\n"
								"	gl_Position = vec4(a_pos, 0.5, 1.0);\n"
								"}\n");

	fragmentShaderSource.append("precision	mediump	float;\n"
								"uniform	lowp sampler2D	u_sampler;\n"
								"varying	vec2			v_texCoord;\n"
								"\n"
								"void main (void)\n"
								"{\n"
								"	gl_FragColor = texture2D(u_sampler, v_texCoord.xy);\n"
								"}\n");

	DE_ASSERT(!m_program);
	m_program = new glu::ShaderProgram(m_context.getRenderContext(), glu::makeVtxFragSources(vertexShaderSource, fragmentShaderSource));

	if (!m_program->isOk())
	{
		m_log << *m_program;
		TCU_FAIL("Failed to create shader program (m_programRender)");
	}

	gl.useProgram (m_program->getProgram());

	// Init GL state

	gl.viewport		(0, 0, VIEWPORT_SIZE, VIEWPORT_SIZE);
	gl.disable		(GL_DEPTH_TEST);
	gl.disable		(GL_CULL_FACE);
	gl.enable		(GL_BLEND);
	gl.blendFunc	(GL_ONE, GL_ONE);
	gl.clearColor	(0.0f, 0.0f, 0.0f, 1.0f);
	gl.clear		(GL_COLOR_BUFFER_BIT);

	deUint32 uSampler	= gl.getUniformLocation(m_program->getProgram(), "u_sampler");
	deUint32 aPos		= gl.getAttribLocation (m_program->getProgram(), "a_pos");
	gl.enableVertexAttribArray	(aPos);
	gl.vertexAttribPointer		(aPos,	2, GL_FLOAT, GL_FALSE, 0, &quadCoords[0]);
	gl.uniform1i				(uSampler, 0);

	// Create texture

	gl.activeTexture	(GL_TEXTURE0);
	gl.genTextures		(1, &m_texture);
	gl.bindTexture		(GL_TEXTURE_2D, m_texture);
	gl.pixelStorei		(GL_UNPACK_ALIGNMENT, m_alignment);
	gl.texParameteri	(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	gl.texParameteri	(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
	gl.texParameteri	(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	gl.texParameteri	(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

	// Prepare texture data

	{
		const tcu::TextureFormat&	texFmt		= glu::mapGLTransferFormat(m_format, m_type);
		int							pixelSize	= texFmt.getPixelSize();
		int							stride		= deAlign32(pixelSize*m_texSize, m_alignment);

		m_texData.resize(stride*m_texSize);

		tcu::PixelBufferAccess		access		(texFmt, m_texSize, m_texSize, 1, stride, 0, &m_texData[0]);

		tcu::fillWithComponentGradients(access, tcu::Vec4(0.0f, 0.0f, 0.0f, 0.0f), tcu::Vec4(1.0f, 1.0f, 1.0f, 1.0f));
	}

	// Do a dry-run to ensure the pipes are hot

	gl.texImage2D	(GL_TEXTURE_2D, 0, m_format, m_texSize, m_texSize, 0, m_format, m_type, &m_texData[0]);
	gl.drawArrays	(GL_TRIANGLE_STRIP, 0, 4);
	gl.finish		();
}

void TextureUploadCase::logResults (void)
{
	const gls::MeasureState& measureState = m_calibrator.getMeasureState();

	// Log measurement details

	m_log << TestLog::Section("Measurement details", "Measurement details");
	m_log << TestLog::Message << "Uploading texture with " << (m_uploadFunction == UPLOAD_TEXIMAGE2D ? "glTexImage2D" : "glTexSubImage2D") << "." << TestLog::EndMessage; // \todo [arttu] Change enum to struct with name included
	m_log << TestLog::Message << "Texture size = "	<< m_texSize	 << "x" << m_texSize	 << "." << TestLog::EndMessage;
	m_log << TestLog::Message << "Viewport size = " << VIEWPORT_SIZE << "x" << VIEWPORT_SIZE << "." << TestLog::EndMessage;
	m_log << TestLog::Message << measureState.numDrawCalls << " upload calls / iteration" << TestLog::EndMessage;
	m_log << TestLog::EndSection;

	// Log results

	TestLog& log = m_testCtx.getLog();
	log << TestLog::Section("Results", "Results");

	// Log individual frame durations
	//for (int i = 0; i < m_calibrator.measureState.numFrames; i++)
	//	m_log << TestLog::Message	<< "Frame "	<< i+1 << " duration: \t" << m_calibrator.measureState.frameTimes[i] << " us."<< TestLog::EndMessage;

	std::vector<deUint64> sortedFrameTimes(measureState.frameTimes.begin(), measureState.frameTimes.end());
	std::sort(sortedFrameTimes.begin(), sortedFrameTimes.end());
	vector<deUint64>::const_iterator first	= sortedFrameTimes.begin();
	vector<deUint64>::const_iterator last	= sortedFrameTimes.end();
	vector<deUint64>::const_iterator middle	= first + (last - first) / 2;

	deUint64 medianFrameTime			=  *middle;
	double medianMTexelsPerSeconds		= (double)(m_texSize*m_texSize*measureState.numDrawCalls) / (double)medianFrameTime;
	double medianTexelDrawDurationNs	= (double)medianFrameTime * 1000.0 / (double)(m_texSize*m_texSize*measureState.numDrawCalls);

	deUint64	totalTime			= measureState.getTotalTime();
	int			numFrames			= (int)measureState.frameTimes.size();
	deInt64		numTexturesDrawn	= measureState.numDrawCalls * numFrames;
	deInt64		numPixels			= (deInt64)m_texSize * (deInt64)m_texSize * numTexturesDrawn;

	double		framesPerSecond			= (double)numFrames / ((double)totalTime / 1000000.0);
	double		avgFrameTime			= (double)totalTime / (double)numFrames;
	double		avgMTexelsPerSeconds	= (double)numPixels / (double)totalTime;
	double		avgTexelDrawDurationNs	= (double)totalTime * 1000.0 / (double)numPixels;

	log << TestLog::Float("FramesPerSecond",	"Frames per second in measurement\t\t",		"Frames/s",		QP_KEY_TAG_PERFORMANCE,	(float)framesPerSecond);
	log << TestLog::Float("AverageFrameTime",	"Average frame duration in measurement\t",	"us",			QP_KEY_TAG_PERFORMANCE,	(float)avgFrameTime);
	log << TestLog::Float("AverageTexelPerf",	"Average texel upload performance\t\t",		"MTex/s",		QP_KEY_TAG_PERFORMANCE,	(float)avgMTexelsPerSeconds);
	log << TestLog::Float("AverageTexelTime",	"Average texel upload duration\t\t",		"ns",			QP_KEY_TAG_PERFORMANCE,	(float)avgTexelDrawDurationNs);
	log << TestLog::Float("MedianTexelPerf",	"Median texel upload performance\t\t",		"MTex/s",		QP_KEY_TAG_PERFORMANCE,	(float)medianMTexelsPerSeconds);
	log << TestLog::Float("MedianTexelTime",	"Median texel upload duration\t\t",			"ns",			QP_KEY_TAG_PERFORMANCE,	(float)medianTexelDrawDurationNs);

	log << TestLog::EndSection;

	gls::logCalibrationInfo(log, m_calibrator);	// Log calibration details
	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, de::floatToString((float)avgMTexelsPerSeconds, 2).c_str());
}

// Texture upload call case

class TextureUploadCallCase : public TextureUploadCase
{
public:
							TextureUploadCallCase	(Context& context, const char* name, const char* description, UploadFunction uploadFunction, deUint32 format, deUint32 type, int texSize);
							~TextureUploadCallCase	(void);

	IterateResult			iterate					(void);
	void					render					(void);
};

TextureUploadCallCase::TextureUploadCallCase (Context& context, const char* name, const char* description, UploadFunction uploadFunction, deUint32 format, deUint32 type, int texSize)
	: TextureUploadCase (context, name, description, uploadFunction, format, type, texSize)
{
}

TextureUploadCallCase::~TextureUploadCallCase (void)
{
	TextureUploadCase::deinit();
}

void TextureUploadCallCase::render (void)
{
	const glw::Functions& gl = m_context.getRenderContext().getFunctions();

	// Draw multiple quads to ensure enough workload

	switch (m_uploadFunction)
	{
		case UPLOAD_TEXIMAGE2D:
			gl.texImage2D(GL_TEXTURE_2D, 0, m_format, m_texSize, m_texSize, 0, m_format, m_type, &m_texData[0]);
			break;
		case UPLOAD_TEXSUBIMAGE2D:
			gl.texSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_texSize, m_texSize, m_format, m_type, &m_texData[0]);
			break;
		default:
			DE_ASSERT(false);
	}
}

tcu::TestNode::IterateResult TextureUploadCallCase::iterate (void)
{
	const glw::Functions& gl = m_context.getRenderContext().getFunctions();

	if (m_testCtx.getTestResult() == QP_TEST_RESULT_NOT_SUPPORTED)
		return STOP;

	for (;;)
	{
		gls::TheilSenCalibrator::State state = m_calibrator.getState();

		if (state == gls::TheilSenCalibrator::STATE_MEASURE)
		{
			int			numCalls	= m_calibrator.getCallCount();
			deUint64	startTime	= deGetMicroseconds();

			for (int i = 0; i < numCalls; i++)
				render();

			gl.finish();

			deUint64	endTime		= deGetMicroseconds();
			deUint64	duration	= endTime-startTime;

			m_calibrator.recordIteration(duration);
		}
		else if (state == gls::TheilSenCalibrator::STATE_RECOMPUTE_PARAMS)
		{
			m_calibrator.recomputeParameters();
		}
		else
		{
			DE_ASSERT(state == gls::TheilSenCalibrator::STATE_FINISHED);
			break;
		}

		// Touch watchdog between iterations to avoid timeout.
		{
			qpWatchDog* dog = m_testCtx.getWatchDog();
			if (dog)
				qpWatchDog_touch(dog);
		}
	}

	GLU_EXPECT_NO_ERROR(gl.getError(), "iterate");
	logResults();
	return STOP;
}

// Texture upload and draw case

class TextureUploadAndDrawCase : public TextureUploadCase
{
public:
								TextureUploadAndDrawCase	(Context& context, const char* name, const char* description, UploadFunction uploadFunction, deUint32 format, deUint32 type, int texSize);
								~TextureUploadAndDrawCase	(void);

	IterateResult				iterate						(void);
	void						render						(void);

private:
	bool						m_lastIterationRender;
	deUint64					m_renderStart;
};

TextureUploadAndDrawCase::TextureUploadAndDrawCase (Context& context, const char* name, const char* description, UploadFunction uploadFunction, deUint32 format, deUint32 type, int texSize)
	: TextureUploadCase		(context, name, description, uploadFunction, format, type, texSize)
	, m_lastIterationRender	(false)
	, m_renderStart			(0)
{
}

TextureUploadAndDrawCase::~TextureUploadAndDrawCase (void)
{
	TextureUploadCase::deinit();
}

void TextureUploadAndDrawCase::render (void)
{
	const glw::Functions& gl = m_context.getRenderContext().getFunctions();

	// Draw multiple quads to ensure enough workload

	switch (m_uploadFunction)
	{
		case UPLOAD_TEXIMAGE2D:
			gl.texImage2D(GL_TEXTURE_2D, 0, m_format, m_texSize, m_texSize, 0, m_format, m_type, &m_texData[0]);
			break;
		case UPLOAD_TEXSUBIMAGE2D:
			gl.texSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_texSize, m_texSize, m_format, m_type, &m_texData[0]);
			break;
		default:
			DE_ASSERT(false);
	}

	gl.drawArrays(GL_TRIANGLE_STRIP, 0, 4);
}

tcu::TestNode::IterateResult TextureUploadAndDrawCase::iterate (void)
{
	if (m_testCtx.getTestResult() == QP_TEST_RESULT_NOT_SUPPORTED)
		return STOP;

	if (m_lastIterationRender && (m_calibrator.getState() == gls::TheilSenCalibrator::STATE_MEASURE))
	{
		deUint64 curTime = deGetMicroseconds();
		m_calibrator.recordIteration(curTime - m_renderStart);
	}

	gls::TheilSenCalibrator::State state = m_calibrator.getState();

	if (state == gls::TheilSenCalibrator::STATE_MEASURE)
	{
		// Render
		int	numCalls = m_calibrator.getCallCount();

		m_renderStart			= deGetMicroseconds();
		m_lastIterationRender	= true;

		for (int i = 0; i < numCalls; i++)
			render();

		return CONTINUE;
	}
	else if (state == gls::TheilSenCalibrator::STATE_RECOMPUTE_PARAMS)
	{
		m_calibrator.recomputeParameters();
		m_lastIterationRender = false;
		return CONTINUE;
	}
	else
	{
		DE_ASSERT(state == gls::TheilSenCalibrator::STATE_FINISHED);
		GLU_EXPECT_NO_ERROR(m_context.getRenderContext().getFunctions().getError(), "finish");
		logResults();
		return STOP;
	}
}

// Texture upload tests

TextureUploadTests::TextureUploadTests (Context& context)
	: TestCaseGroup(context, "upload", "Texture upload tests")
{
}

TextureUploadTests::~TextureUploadTests (void)
{
	TextureUploadTests::deinit();
}

void TextureUploadTests::deinit (void)
{
}

void TextureUploadTests::init (void)
{
	TestCaseGroup* uploadCall		= new TestCaseGroup(m_context, "upload",			"Texture upload");
	TestCaseGroup* uploadAndDraw	= new TestCaseGroup(m_context, "upload_draw_swap",	"Texture upload, draw & buffer swap");

	addChild(uploadCall);
	addChild(uploadAndDraw);

	static const struct
	{
		const char*		name;
		const char*		nameLower;
		UploadFunction	func;
	} uploadFunctions[] =
	{
		{ "texImage2D",		"teximage2d",		UPLOAD_TEXIMAGE2D },
		{ "texSubImage2D",	"texsubimage2d",	UPLOAD_TEXSUBIMAGE2D }
	};

	static const struct
	{
		const char*	name;
		deUint32	format;
		deUint32	type;
	} textureCombinations[] =
	{
		{ "rgb_ubyte",				GL_RGB,				GL_UNSIGNED_BYTE },
		{ "rgba_ubyte",				GL_RGBA,			GL_UNSIGNED_BYTE },
		{ "alpha_ubyte",			GL_ALPHA,			GL_UNSIGNED_BYTE },
		{ "luminance_ubyte",		GL_LUMINANCE,		GL_UNSIGNED_BYTE },
		{ "luminance-alpha_ubyte",	GL_LUMINANCE_ALPHA,	GL_UNSIGNED_BYTE },
		{ "rgb_ushort565",			GL_RGB,				GL_UNSIGNED_SHORT_5_6_5 },
		{ "rgba_ushort4444",		GL_RGBA,			GL_UNSIGNED_SHORT_4_4_4_4 },
		{ "rgba_ushort5551",		GL_RGBA,			GL_UNSIGNED_SHORT_5_5_5_1 },
	};

	static const struct
	{
		int				size;
		TestCaseGroup*	uploadCallGroup;
		TestCaseGroup*	uploadAndDrawGroup;
	} textureSizes[] =
	{
		{ 16,	new TestCaseGroup(m_context, "16x16",		"Texture size 16x16"),		new TestCaseGroup(m_context, "16x16",		"Texture size 16x16") },
		{ 256,	new TestCaseGroup(m_context, "256x256",		"Texture size 256x256"),	new TestCaseGroup(m_context, "256x256",		"Texture size 256x256") },
		{ 257,	new TestCaseGroup(m_context, "257x257",		"Texture size 257x257"),	new TestCaseGroup(m_context, "257x257",		"Texture size 257x257") },
		{ 1024,	new TestCaseGroup(m_context, "1024x1024",	"Texture size 1024x1024"),	new TestCaseGroup(m_context, "1024x1024",	"Texture size 1024x1024") },
		{ 2048,	new TestCaseGroup(m_context, "2048x2048",	"Texture size 2048x2048"),	new TestCaseGroup(m_context, "2048x2048",	"Texture size 2048x2048") },
	};

#define FOR_EACH(ITERATOR, ARRAY, BODY)	\
	for (int ITERATOR = 0; ITERATOR < DE_LENGTH_OF_ARRAY(ARRAY); ITERATOR++)	\
		BODY

	FOR_EACH(uploadFunc,	 uploadFunctions,
	FOR_EACH(texSize,		 textureSizes,
	FOR_EACH(texCombination, textureCombinations,
	{
		string			caseName	= string("") + uploadFunctions[uploadFunc].nameLower + "_" + textureCombinations[texCombination].name;
		UploadFunction	function	= uploadFunctions[uploadFunc].func;
		deUint32		format		= textureCombinations[texCombination].format;
		deUint32		type		= textureCombinations[texCombination].type;
		int				size		= textureSizes[texSize].size;

		textureSizes[texSize].uploadCallGroup->addChild		(new TextureUploadCallCase		(m_context, caseName.c_str(), "", function, format, type, size));
		textureSizes[texSize].uploadAndDrawGroup->addChild	(new TextureUploadAndDrawCase	(m_context, caseName.c_str(), "", function, format, type, size));
	})));

	for (int i = 0; i < DE_LENGTH_OF_ARRAY(textureSizes); i++)
	{
		uploadCall->addChild	(textureSizes[i].uploadCallGroup);
		uploadAndDraw->addChild	(textureSizes[i].uploadAndDrawGroup);
	}
}


} // Performance
} // gles2
} // deqp
